//
//  FileManager.swift
//  NetWork
//
//  Created by Delin Meng 孟德林 on 2018/11/27.
//  Copyright © 2018 Delin Meng 孟德林. All rights reserved.
//


import Foundation

public typealias FileName = String

public struct FDFileAttribute {
    /// 文件大小 (Byte)
    public let size: Int
    /// 创建日期
    public let creationDate: Date
    /// 修改日期
    public let modificationDate: Date
}

public class FDFileManager {
    public static let `default`: FDFileManager = {
        return FDFileManager()
    }()

    // MARK: 增、改

    /// 创建文件
    ///
    /// - Parameters:
    ///   - atPath: 路径
    ///   - contents: 内容
    ///   - attributes: 属性
    /// - Returns: 创建成功或者文件已经存在则返回 true，否则返回 false
    public func createFile(atPath: String, contents: Data? = nil, attributes: [FileAttributeKey: Any]? = nil) throws {
        try checkAndCreateDirectoryIfNeed((atPath as NSString).deletingLastPathComponent)
        if !FileManager.default.createFile(atPath: atPath, contents: contents, attributes: attributes) {
            throw FDFileError.creatFailed
        }
    }

    /// 创建/写入文件
    ///
    /// - Parameters:
    ///   - path: 文件 path，若文件不存在则会创建
    ///   - content: 要写入的字符串
    ///   - append: 续写，默认 true
    /// - Throws: 抛出[创建文件夹]和[创建文件]的系统 error
    public func writeFile(path: String,
                          content: String,
                          append: Bool = true) throws {
        if let data = content.data(using: .utf8) {
            try writeFile(path: path, data: data, append: append)
        } else {
            throw FDFileError.writeFailed(.stringToDataFailed(path))
        }
    }

    /// 创建/写入文件
    ///
    /// - Parameters:
    ///   - path: 文件 path，若文件不存在则会创建
    ///   - data: 要写入的 Data
    ///   - append: 续写，默认 true
    /// - Throws: FDFileError.createDirectoryFailed
    ///           FDFileError.writeFailed
    public func writeFile(path: String,
                          data: Data,
                          autoClose: Bool = true,
                          append: Bool = true) throws {

        if !fileExists(atPath: path) {
            try createFile(atPath: path)
        }

        if append {
            // 续写
            if let fileHandler = FileHandle(forUpdatingAtPath: String(path)){
                // 跳到文件末尾
                fileHandler.seekToEndOfFile()
                // 追加数据
                fileHandler.write(data)

                // 关闭文件
                fileHandler.closeFile()
            }
        } else {
            // 覆写
            do {
                try data.write(to: URL(fileURLWithPath: path), options: [.atomic])
            } catch {
                throw FDFileError.writeFailed(.createFileFailed(path, error))
            }
        }
    }

    /// 检查目录如果不存在则创建之
    ///
    /// - Parameter path: 目录路径
    /// - Throws: FDFileError.createDirectoryFailed
    private func checkAndCreateDirectoryIfNeed(_ directoryPath: String) throws {
        do {
            if !fileExists(atPath: directoryPath) {
                try FileManager.default.createDirectory(atPath: directoryPath,
                                                        withIntermediateDirectories: true,
                                                        attributes: nil)
            }
        } catch {
            throw FDFileError.writeFailed(.createDirectoryFailed(directoryPath, error))
        }
    }

    // MARK: 删

    /// 删除文件
    ///
    /// - Parameter path: 文件路径
    public func removeFile(path: String) throws {
        try FileManager.default.removeItem(atPath: path)
    }

    // MARK: 查

    /// 查询文件是否存在
    ///
    /// - Parameter path: 文件路径
    /// - Returns: 是否存在
    public func fileExists(atPath path: String) -> Bool {
        return FileManager.default.fileExists(atPath: path)
    }

    /// 获取文件的属性
    ///
    /// - Parameter path: FDFileAttribute 对象
    public func fileAttributes(path: String) throws -> FDFileAttribute{
        let dictionary = try FileManager.default.attributesOfItem(atPath: path) as [FileAttributeKey: Any]
        guard let size = dictionary[.size] as? Int else {
            throw FDFileError.fetchFileAttributeFailed("size")
        }
        guard let creationDate = dictionary[.creationDate] as? Date else {
            throw FDFileError.fetchFileAttributeFailed("creationDate")
        }
        guard let modificationDate = dictionary[.modificationDate] as? Date else {
            throw FDFileError.fetchFileAttributeFailed("modificationDate")
        }
        return FDFileAttribute(size: size,
                               creationDate: creationDate,
                               modificationDate: modificationDate)
    }

    /// 读取文件
    ///
    /// - Parameter path: 文件路径
    /// - Returns: 文件内容字符串
    public func readFile(path: String) throws -> String {
        let fileData: Data = try readFile(path: path)
        guard let content = String(data: fileData, encoding: .utf8) else {
            // data to string 失败
            throw FDFileError.readFailed(.dataToStringFailed(path))
        }
        return content
    }

    /// 读取文件
    ///
    /// - Parameter path: 文件路径
    /// - Returns: 文件内容字符串
    public func readFile(path: String) throws -> Data {
        guard fileExists(atPath: String(path)) else {
            // 文件不存在
            throw FDFileError.readFailed(.fileNotExists(path))
        }
        guard let fileHandle = FileHandle(forReadingAtPath: path) else {
            // fileHandle 初始化失败
            throw FDFileError.readFailed(.fileHandleInitFailed(path))
        }
        let data = fileHandle.readDataToEndOfFile()
        fileHandle.closeFile()
        return data
    }

    /// 获取目标文件夹下所有文件名
    ///
    /// - Parameter folderPath: 文件夹路径
    /// - Returns: 文件名数组
    public func contentsOfDirectory(atPath folderPath: String) throws -> [FileName] {
        let files = try FileManager.default.contentsOfDirectory(atPath: folderPath)
        return files
    }

    /// 获取目标文件夹下所有文件名（可过滤）
    ///
    /// - Parameters:
    ///   - folderPath: 文件夹路径
    ///   - filter: 对结果数组进行 filter
    /// - Returns: （过滤后的）文件名数组
    /// - Throws: 1.获取所有文件名时抛出。2.数组过滤时抛出。
    public func contentsOfDirectory(atPath folderPath: String,
                                    filter: ((String) throws -> Bool)? = nil) throws -> [FileName] {
        let files = try FileManager.default.contentsOfDirectory(atPath: folderPath)
        if let filter = filter {
            return try files.filter(filter)
        } else {
            return files
        }
    }

    /// 获取目标文件夹下包含特定字符串的文件名
    ///
    /// - Parameters:
    ///   - folderPath: 文件夹路径
    ///   - subString: 特定字符串
    /// - Returns: [文件名]
    public func contentsOfDirectory(atPath folderPath: String,
                                    subString: String) throws -> [FileName] {
        return try self.contentsOfDirectory(atPath: folderPath, filter: {$0.contains(subString)})
    }

    /// 获取目标文件夹下特定前缀文件名
    ///
    /// - Parameters:
    ///   - folderPath: 文件夹路径
    ///   - prefix: 前缀
    /// - Returns: [文件名]
    public func contentsOfDirectory(atPath folderPath: String,
                                    prefix: String) throws -> [FileName] {
        return try self.contentsOfDirectory(atPath: folderPath, filter: {$0.hasPrefix(prefix)})
    }

    /// 获取目标文件夹下特定后缀文件名
    ///
    /// - Parameters:
    ///   - folderPath: 文件夹路径
    ///   - suffix: 前缀
    /// - Returns: [文件名]
    public func contentsOfDirectory(atPath folderPath: String,
                                    suffix: String) throws -> [FileName] {
        return try self.contentsOfDirectory(atPath: folderPath, filter: {$0.hasSuffix(suffix)})
    }
}
